/**
 * 
 */
package RecordManagement;

import PagedFile.*;

/**
 * @author Neil
 *
 */
public class RM_FileHandle implements RM_FileHandle_Interface {

	PF_Manager pfm;
	PF_FileHandle pfFileHandle;
	RM_FileHead rmFileHead;
	static FunctionalClass.Bit_Operation bop = new FunctionalClass.Bit_Operation();
	static FunctionalClass.Buffer_Bit_Operation bbop = new FunctionalClass.Buffer_Bit_Operation();
	
	public RM_FileHandle(PF_FileHandle pfFileHandle, RM_FileHead rmFileHead, PF_Manager pfm){
		this.pfFileHandle = pfFileHandle;
		this.rmFileHead = rmFileHead;
		this.pfm = pfm;
	}
	
	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileHandle_Interface#DeleteRec(RecordManagement.RID)
	 */
	@Override
	public void DeleteRec(RID rid) {
		// TODO Auto-generated method stub
		int pageNum = rid.GetPageNum();
		int slotNum = rid.GetSlotNum();
		
		Buffer dataBuffer = pfFileHandle.GetThisPage(pageNum);
		
		if ( !bbop.bitTest(dataBuffer.contents, RM_Manager.RM_PAGEHEAD_SIZE, slotNum) )
			throw new RM_Abort_Exception();
		
		dataBuffer.mark(this.hashCode());
		/* unSet the bitmap */
		bbop.bitUnSet(dataBuffer.contents, RM_Manager.RM_PAGEHEAD_SIZE, slotNum);
		/* decrement the number of records */
		dataBuffer.contents.position(0);
		int nRecord = dataBuffer.contents.getInt();
		--nRecord;
		dataBuffer.contents.position(0);
		dataBuffer.contents.putInt(nRecord);
		
		pfm.DisposeBlock(dataBuffer);
	}

	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileHandle_Interface#ForcePages(int)
	 */
	@Override
	public void ForcePage(int pageNum) {
		// TODO Auto-generated method stub
		this.pfFileHandle.ForcePages(pageNum);
	}

	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileHandle_Interface#GetRec(RecordManagement.RID)
	 */
	@Override
	public RM_Record GetRec(RID rid) {
		// TODO Auto-generated method stub
		if (!pfm.CheckOpen(pfFileHandle))
			throw new RM_Abort_Exception();

		int pageNum = rid.GetPageNum();
		int slotNum = rid.GetSlotNum();
		
		if ( !bop.bitTest(this.pfFileHandle.fhead.bitmap, pageNum) )
			throw new RM_Abort_Exception();
		
		Buffer dataBuffer = pfFileHandle.GetThisPage(pageNum);
		
		if ( !bbop.bitTest(dataBuffer.contents, RM_Manager.RM_PAGEHEAD_SIZE, slotNum))
			throw new RM_Abort_Exception();
		
		byte[] data = new byte[rmFileHead.recordSize];
		dataBuffer.contents.position(rmFileHead.firstRecordOffset + rmFileHead.recordSize*slotNum);
		dataBuffer.contents.get(data);
		
		pfm.DisposeBlock(dataBuffer);
		
		return new RM_Record(new RID(rid), data);
	}

	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileHandle_Interface#InsertRec(byte[])
	 */
	@Override
	public RID InsertRec(byte[] recordData) {
		// TODO Auto-generated method stub
		if (!pfm.CheckOpen(pfFileHandle))
			throw new RM_Abort_Exception();
		int pageNum = 1;
		do {
			if ( pfFileHandle.isAllocated(pageNum) && !isFull(pageNum) )
				break;
			else
				++pageNum;
		} while (pageNum <= this.pfFileHandle.fhead.lastPageNum);
		
		Buffer pageBuffer = null;
		/*
		 * if pageNum > lastPageNum,
		 * there is need to allocate a new page, and modify PF_FileHead.
		 * Or else, just read the page from the file.
		 */
		if (pageNum > this.pfFileHandle.fhead.lastPageNum) {
			pageBuffer = pfFileHandle.AllocatePage();
			pageBuffer.contents.position(0);
			pageBuffer.contents.putInt(0);
			pageNum = pageBuffer.block.pageNum;
		} else {
			pageBuffer = pfFileHandle.GetThisPage(pageNum);
		}
		
		byte[] recBitMap = new byte[(rmFileHead.recordPerPage+8)/8];
		pageBuffer.contents.position(0);
		int nRecord = pageBuffer.contents.getInt(); //get number of record in the page
		pageBuffer.contents.get(recBitMap);
		int slotNum;
		/* Find the free slot to fill new record */
		for (slotNum = 0; slotNum<rmFileHead.recordPerPage; ++slotNum) {
			if ( !bop.bitTest(recBitMap, slotNum) )
				break;
		}
		/* Since the page is not full, a free slot must be found. */
		if ( slotNum == rmFileHead.recordPerPage )
			throw new RM_Abort_Exception();

		pageBuffer.mark(this.hashCode());
		/* put into the record data */
		pageBuffer.contents.position(rmFileHead.firstRecordOffset + rmFileHead.recordSize*slotNum);
		pageBuffer.contents.put(recordData);
		/* set the number of records */
		pageBuffer.contents.position(0);
		pageBuffer.contents.putInt(++nRecord);
		/* set the bitmap */
		bbop.bitSet(pageBuffer.contents, RM_Manager.RM_PAGEHEAD_SIZE, slotNum);
		
		if ( nRecord == rmFileHead.recordPerPage ) 
			bop.bitSet(rmFileHead.bitmap, pageNum);
		
		pfm.DisposeBlock(pageBuffer);
		
		return new RID(pageNum, slotNum);
	}

	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileHandle_Interface#UpdateRec(RecordManagement.RM_Record)
	 */
	@Override
	public void UpdateRec(RM_Record rec) {
		// TODO Auto-generated method stub
		int pageNum = rec.rid.GetPageNum();
		int slotNum = rec.rid.GetSlotNum();
		
		Buffer dataBuffer = this.pfFileHandle.GetThisPage(pageNum);
		
		if ( !bbop.bitTest(dataBuffer.contents, RM_Manager.RM_PAGEHEAD_SIZE, slotNum))
			throw new RM_Abort_Exception();
		
		byte[] data = rec.data;
		dataBuffer.mark(this.hashCode());
		dataBuffer.contents.position(this.rmFileHead.firstRecordOffset+this.rmFileHead.recordSize*slotNum);
		dataBuffer.contents.put(data);
		
		pfm.DisposeBlock(dataBuffer);
	}
	
	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileHandle_Interface#ForceAllPages()
	 */
	@Override
	public void ForceAllPages() {
		// TODO Auto-generated method stub
		this.pfFileHandle.ForcePages();
	}
	
	@Override
	public int GetNumPages() {
		// TODO Auto-generated method stub
		return this.pfFileHandle.fhead.nAllocatedPages;
	}
	
	@Override
	public int GetNumRecords() {
		// TODO Auto-generated method stub
		return this.rmFileHead.numOfRecords;
	}
	
	private boolean isFull(int pageNum) {
		return RM_FileHead.bop.bitTest(rmFileHead.bitmap, pageNum);
	}

}
